﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Com.Guocz.Media.FileSystem.Crypt;

namespace Com.Guocz.Media.FileSystem
{
    public class FileSystem : IFileSystem
    {
        private Path root;
        private IndexManager indexManager;
        private DataManager dataManager;
        private ICrypt cryptor;

        private FileSystem(string name, string pass, MetaInfo meta)
        {
            this.cryptor = new Crypt.RijndaelCrypt(name, pass);
            this.indexManager = new IndexManager(meta, cryptor);
            this.dataManager = new DataManager(meta);

            root = indexManager.Load();
        }

        public static FileSystem Create(string name, string pass, MetaInfo meta)
        {
            if (!CanCreate(meta))
            {
                throw new IOException("one or more file already exist:" + meta);
            }
            return new FileSystem(name, pass, meta);
        }

        public static FileSystem Open(string name, string pass, MetaInfo meta)
        {
            if (!CanOpen(meta))
            {
                throw new FileNotFoundException("one or more file can not found:" + meta);
            }
            return new FileSystem(name, pass, meta);
        }

        #region IFileSystem Members

        public OutputStream create(string path)
        {
            if (GetPath(path) != null)
            {
                throw new InvalidArgumentException("file already exist:" + path);
            }
            Path p = new Path(GetName(path));
            p.IsDirectory = false;

            Path father = GetFather(path);
            p.Father = father;
            father.AddChild(p);
            OutputStream stream = new OutputStream(p, dataManager, cryptor);
            stream.onClose += new OutputFinished(stream_onClose);
            return stream;
        }

        void stream_onClose()
        {
            indexManager.Store(root);
        }

        private Path GetFather(string path)
        {
            string fatherpath = GetFatherPath(path);
            mkdirs(fatherpath);
            return GetPath(fatherpath);
        }

        private Path GetPath(string path)
        {
            List<string> names = GetNameList(path);
            Path current = root;
            foreach (var name in names)
            {
                if (current != null)
                {
                    current = current.GetChild(name);
                } else 
                {
                    break;
                }
            }

            return current;
        }

        #endregion

        #region
        private static string normalize(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                path = Const.SEPARATOR;
            }
            path = path.Trim();
            if (!path.StartsWith(Const.SEPARATOR))
            {
                path = Const.SEPARATOR + path;
            }
            if (path.Length > 1 && path.EndsWith(Const.SEPARATOR))
            {
                path = path.Substring(0, path.Length - 1);
            }

            return path;
        }

        private static bool CanOpen(MetaInfo meta)
        {
            return File.Exists(meta.IndexFile)
                && File.Exists(meta.DataMale)
                && File.Exists(meta.DataFemale);
        }

        private static bool CanCreate(MetaInfo meta)
        {
            return !File.Exists(meta.IndexFile)
                && !File.Exists(meta.DataMale)
                && !File.Exists(meta.DataFemale);
        }

        private static string GetName(string path)
        {
            path = normalize(path);
            return path.Substring(path.LastIndexOf(Const.SEPARATOR) + 1);
        }

        private static string GetFatherPath(string path)
        {
            path = normalize(path);
            return path.Substring(0, path.LastIndexOf(Const.SEPARATOR));
        }

        private static List<string> GetNameList(string path)
        {
            path = normalize(path);
            string[] p = path.Split(new string[] { Const.SEPARATOR }, StringSplitOptions.RemoveEmptyEntries);
            List<string> fathers = new List<string>();
            for (int i = 0; i < p.Length; i++)
            {
                fathers.Add(p[i]);
            }

            return fathers;
        }
        #endregion

        #region IFileSystem Members

        public InputStream open(string path)
        {
            Path p = GetPath(path);
            if (p == null)
            {
                throw new InvalidArgumentException("file not exist:" + path);
            }
            else if (p.IsDirectory)
            {
                throw new InvalidArgumentException("can not open a directory:" + path);
            }
            return new InputStream(p, dataManager, cryptor);
        }

        public bool rename(string src, string dst)
        {
            string srcf = GetFatherPath(src);
            string srcd = GetFatherPath(dst);
            if (srcf.Equals(srcd))
            {
                string srcname = GetName(src);
                string desname = GetName(dst);
                Path father = GetPath(srcf);
                father.rename(srcname, desname);

                indexManager.Store(root);
                return true;
            }
            else
            {
                return false;
            }
        }

        private List<string> allFile(string path)
        {
            List<string> files = new List<string>();
            Path p = GetPath(path);
            if (p.IsDirectory)
            {
                foreach (var item in p.Children)
                {
                    files.AddRange(allFile(item.AbsolutePath));
                }
            }
            else
            {
                files.Add(p.AbsolutePath);
            }

            return files;
        }

        public void delete(string path)
        {
            List<string> files = allFile(path);
            foreach (var file in files)
            {
                Path f = GetPath(file);
                foreach (var item in f.BlockMetaList)
                {
                    dataManager.Delete(item);
                }
            }
            Path p = GetPath(path);
            p.Father.DeleteChild(p.Name);
            
            indexManager.Store(root);
        }

        public void mkdirs(string f)
        {
            List<string> names = GetNameList(f);
            Path current = root;
            foreach (var name in names)
            {
                if (current.GetChild(name) == null)
                {
                    Path tmp = new Path(name);
                    tmp.Father = current;
                    current.AddChild(tmp);

                    current = tmp;
                }
                else
                {
                    current = current.GetChild(name);
                }
            }
            indexManager.Store(root);
        }

        public Path Root
        {
            get { return root; }
        }

        #endregion

        #region IFileSystem Members


        public Path getPath(string path)
        {
            return GetPath(path);
        }

        #endregion
    }
}
